/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package io.midi;

import de.humatic.mmj.MidiInput;
import de.humatic.mmj.MidiListener;
import de.humatic.mmj.MidiOutput;
import de.humatic.mmj.MidiSystem;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiMessage;
import tal.drivechain.midi.MidiUtility;
import tal.drivechain.midi.WShortMessage;
import tal.drivechain.midi.WSysexMessage;

/**
 *
 * @author eisental
 */
public class CoreMidiController extends MidiSystemController implements MidiListener {
    private MidiOutput output = null;
    private MidiInput input = null;

    public void open() throws IllegalArgumentException {
        if (portId==-1)
            throw new IllegalArgumentException("Trying to open port while no port is selected.");
        else {
            try {
                if (isInput) {
                    if (input!=null) input.close();
                    input = MidiSystem.openMidiInput(portId);
                    input.addMidiListener(this);
                } else {
                    if (output!=null) output.close();
                    output = MidiSystem.openMidiOutput(portId);
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new IllegalArgumentException("Unknown device port id: " + portId);
            }
        }
    }

    public void close() throws IllegalArgumentException {
        if (input!=null)
            input.close();
        else if (output!=null)
            output.close();
        input = null; output = null;
    }

    public void midiInput(byte[] midi) {
        if (!isInput)
            throw new IllegalArgumentException("Received midi input while midi controller is set to output midi.");

        MidiMessage msg = null;
        try {
           msg = new WShortMessage(midi);
        } catch (InvalidMidiDataException me) {
            try {
                msg = new WSysexMessage(midi);
            } catch (InvalidMidiDataException ex) {
                String smid = "";
                for (byte b : midi)
                    smid += b + "-";
                throw new IllegalArgumentException("Invalid incoming midi data: " + smid);
            }
        }
        listener.receivedMidi(msg, -1);
    }

    @Override
    public void sendMidiMessage(MidiMessage msg, long timestamp) throws IllegalArgumentException {
        if (isInput)
            throw new IllegalArgumentException("Trying to send out midi message while controller is set to midi input.");
        try {
            if (!isOpen())
                throw new IllegalArgumentException("Trying to send midi message while midi device is not open.");
            else output.sendMidi(msg.getMessage(), timestamp);
        } catch (IllegalStateException ie) {
            throw new IllegalArgumentException("While sending " + MidiUtility.midiToString(msg) + ": " + ie.getMessage());
        }
    }

    @Override
    public boolean isOpen() {
        if (isInput)
            return (input!=null);
        else return (output!=null);
    }

    @Override
    public String getPortName() {
        if (isInput) {
            if (portId>=MidiSystem.getInputs().length) portId = -1;
            if (portId==-1) return "unknown";
            else return MidiSystem.getInputs()[portId];
        } else {
            if (portId>=MidiSystem.getOutputs().length) portId = -1;
            if (portId==-1) return "unknown";
            else return MidiSystem.getOutputs()[portId];
        }
    }

    public void createVirtualPorts(String virtual) {
        try {
            MidiSystem.closeMidiSystem();
        } catch (UnsatisfiedLinkError e) {}
        if (virtual==null) {
            MidiSystem.initMidiSystem(null, null);
        } else if (isInput) {
            MidiSystem.initMidiSystem("internal." + virtual, virtual);
        } else  {
            MidiSystem.initMidiSystem(virtual, "internal." + virtual);
        }
    }

    @Override
    public int findPortByName(String name) {
        String[] ports;
        if (isInput) ports = MidiSystem.getInputs();
        else ports = MidiSystem.getOutputs();
        for (int i=0; i<ports.length; i++) {
            if (ports[i].indexOf(name)!=-1)
                return i;
        }
        throw new IllegalArgumentException("Can't find id of port '" + name + "'");
    }

}
